//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
//

#include <omnetpp.h>
#include "Structs.h"
#include "UDPBasicApp.h"
#include "Messages_m.h"

class CDNApp : public UDPBasicApp {
	IPvXAddress addr;
	std::string s_addr;
	IPvXAddress trackerAddr;
	std::string s_trackerAddr;
	std::vector<SubStream> uploads;
	unsigned maxConnections;
	int graceChunks;
	int id;
	bool ranked;
	
	protected:
		virtual void initialize(int);
		virtual void handleMessage (cMessage *msg);
		bool periodicSend();
		void processPing (Ping *);
		void processRequest(StreamReq *);
		void disconnectPeer(PeerExit *);

	public:
		CDNApp();
};

Define_Module(CDNApp);

CDNApp::CDNApp() {
	UDPBasicApp::UDPBasicApp();
}

void CDNApp::initialize(int stage) {

	// because of IPAddressResolver, we need to wait until interfaces are registered,
	// address auto-assignment takes place etc.
	if (stage!=3)
        	return;

	counter = 0;
	numSent = 0;
	numReceived = 0;
	WATCH(numSent);
	WATCH(numReceived);

	localPort = par("localPort");
	destPort = par("destPort");

	const char *destAddrs = par("destAddresses");
	cStringTokenizer tokenizer(destAddrs);
	const char *token;
	while ((token = tokenizer.nextToken())!=NULL)
        	destAddresses.push_back(IPAddressResolver().resolve(token));

	//if (destAddresses.empty())
        //	return;

	bindToPort(localPort);

	maxConnections = par("maxConnections");
	graceChunks = par("graceChunks");
	id = par("id");
	ranked = (bool) par("rank");
	if (ranked)
		ev << getFullPath() << ": Ranked\n";
	
	cMessage *c = new cMessage("ConfigureMsg", CONFIGURE);
	scheduleAt(simTime() + 1,c);
}

//Retorna true se ainda houver dados a ser enviados
bool CDNApp::periodicSend() {
	
	bool cont = false;
	IPvXAddress dst;
	Data *data;
	unsigned i = 0;
	
	if (uploads.size() > 0) {
				
		ev << getFullPath() << ": Uploading to " << uploads.size() << " peers at " << simTime() << "\n";
		while (i < uploads.size()) {
			//manda data
			data = new Data();
			data->setPeerID(id);
			data->setStreamID(uploads[i].getStreamID());
			data->setSubStreamID(uploads[i].getSubStreamID());
			data->setSrcAddr(s_addr.c_str());
			data->setDstAddr(uploads[i].getPeerAddr().c_str());
			data->setByteLength(par("messageLength").longValue());
			data->setTimestamp(SIMTIME_DBL(simTime()));
			if (dst.tryParse(uploads[i].getPeerAddr().c_str())) {
				sendToUDP (data, localPort, dst, destPort);
				ev << getFullPath() << ": Send data to " << uploads[i].getPeerAddr() << " Stream " << uploads[i].getStreamID()
				<< " Substream " << uploads[i].getSubStreamID() << "\n";
				//Atualiza
				uploads[i].update();
			}
			else {
				ev << getFullPath() << ": IPxAddress Parse error " << uploads[i].getPeerAddr() << "\n";
				delete data;
			}
					
			i++;
			cont = true;
		}
		
	}

	return cont;
}

//Se for para ele, responde o ping
void CDNApp::processPing(Ping *p) {

	if (p->getDstAddr() == s_addr) {
		IPvXAddress dst;
		Pong *r = new Pong();
		r->setSrcAddr(s_addr.c_str());
		r->setDstAddr(p->getSrcAddr());
		ev << getFullPath() << ": Reply ping to peer " << r->getDstAddr() << "\n";
		if (dst.tryParse(r->getDstAddr()))
			sendToUDP (r, localPort, dst, destPort);
		else {
			ev << getFullPath() << ": IPxAddress Parse error " << r->getDstAddr() << "\n";
			delete r;
		}
		
	}
}


//Processa Stream Requests
void CDNApp::processRequest(StreamReq *sreq) {

	IPvXAddress dst;
	PeerExit *pext;
	Data *data;
	bool serve = true;

	if (uploads.size() >= maxConnections) {
		if (!ranked) {
			//Carga maxima: Nega o pedido ao peer
			serve = false;
		}
		else {
			//Procura o peer com menor rank
			int minR = sreq->getRank();
			unsigned j = 0;
			for (unsigned i = 0; i < uploads.size(); i++)
				if (uploads[i].getRank() < minR) {
					minR = uploads[i].getRank();
					j = i;
				}
			
			//Descarta o peer de menor rank
			if (minR == sreq->getRank()) {
				//O peer de menor rank eh o emissor do request. Nada acontece
				serve = false;
			}
			else {
				pext = new PeerExit();
				pext->setPeerID(id);
				pext->setStreamID(uploads[j].getStreamID());
				pext->setSubStreamID(uploads[j].getSubStreamID());
				pext->setSrcAddr(s_addr.c_str());
				pext->setDstAddr(uploads[j].getPeerAddr().c_str());
				pext->setCause(EXIT_LOAD);
				ev << getFullPath() << ": Will not serve " << uploads[j].getPeerAddr() << " Peer " << uploads[j].getPeerID() << "\n";
				if (dst.tryParse(uploads[j].getPeerAddr().c_str())) {
					sendToUDP (pext, localPort, dst, destPort);
				}
				else {
					ev << getFullPath() << ": IPxAddress Parse error " << uploads[j].getPeerAddr() << "\n";
					delete pext;
				}
				//Remove da lista
				uploads.erase(uploads.begin()+j);
				if (ev.isGUI())
					getParentModule()->bubble("Used Rank");
			}
					
		
		}
		
	}
	
	if (serve) {
		//envio de dados e registro do peer
		ev << getFullPath() << ": Uploading to " << sreq->getSrcAddr();
		data = new Data();
		data->setPeerID(id);
		data->setStreamID(sreq->getStreamID());
		data->setSubStreamID (sreq->getSubStreamID());
		data->setSrcAddr(s_addr.c_str());
		data->setDstAddr(sreq->getSrcAddr());
		data->setTimestamp(SIMTIME_DBL(simTime()));
		data->setByteLength(par("messageLength").longValue());
		if (dst.tryParse(sreq->getSrcAddr())) {
			sendToUDP (data, localPort, dst, destPort);
			ev << getFullPath() << ": Send data to " << sreq->getSrcAddr() << " Stream " << sreq->getStreamID()
					<< " Substream " << sreq->getSubStreamID() << "\n";
			SubStream *s = new SubStream(sreq->getSubStreamID(), sreq->getStreamID(), sreq->getPeerID(), sreq->getSrcAddr(), sreq->getRank());
			s->update();
			uploads.push_back(*s);
			
			//Agendando envio periodico de mensagens
			if (uploads.size() == 1) {
				DataTimer *timer = new DataTimer();
				scheduleAt(simTime()+par("sendDataTime"),timer);
			}
		}
		else {
			ev << getFullPath() << ": IPxAddress Parse error " << sreq->getSrcAddr() << "\n";
			delete data;
		}
	}
	else {
		//Rejeita o pedido
		pext = new PeerExit();
		pext->setPeerID(id);
		pext->setStreamID(sreq->getStreamID());
		pext->setSubStreamID(sreq->getSubStreamID());
		pext->setSrcAddr(s_addr.c_str());
		pext->setDstAddr(sreq->getSrcAddr());
		pext->setCause(EXIT_LOAD);
		ev << getFullPath() << ": Will not serve " << sreq->getSrcAddr() << " Peer " << sreq->getPeerID() << "\n";
		if (dst.tryParse(sreq->getSrcAddr())) {
			sendToUDP (pext, localPort, dst, destPort);
		}
		else {
			ev << getFullPath() << ": IPxAddress Parse error " << sreq->getSrcAddr() << "\n";
			delete pext;
		}
	}
}

void CDNApp::disconnectPeer(PeerExit *pext) {
	unsigned i = 0;
	while (i < uploads.size())
		if (uploads[i].compareTo(pext->getSubStreamID(), pext->getStreamID(), pext->getPeerID()))
			uploads.erase(uploads.begin()+i);
		else
			i++;
}

void CDNApp::handleMessage(cMessage *msg) {
	
	General *gmsg;
	Advertisement *padv;
	int type;

	if (msg->isSelfMessage()) {
		switch (msg->getKind()) {
			case CONFIGURE:
				addr = IPAddressResolver().resolve(getParentModule()->getFullName());
				s_addr = addr.str();
				trackerAddr = IPAddressResolver().resolve("tracker");
				s_trackerAddr = trackerAddr.str();
				//Registro junto ao tracker
				trackerAddr = IPAddressResolver().resolve("tracker");
				padv = new Advertisement();
				padv->setPeerID(id);
				padv->setStreamID(CDN_FLAG);
				padv->setSrcAddr(s_addr.c_str());
				padv->setDstAddr(s_trackerAddr.c_str());
				ev << getFullPath() << ": Advertising tracker " << padv->getDstAddr() << "\n";
				sendToUDP (padv, localPort, trackerAddr, destPort);
				
				break;
			
			case DATATIMER:
				if(periodicSend()) 
					scheduleAt(simTime()+par("sendDataTime"),msg->dup());
				break;
			default:
				break;
				
		}
	}
	else {
		//gmsg = (General *) msg;
		
		if (dynamic_cast<General *> (msg) != NULL) {
			gmsg = (General *) msg;
			type = gmsg->getType();
		}
		else
			type = -1;
		switch (type) {
			case PING:
				processPing((Ping *) gmsg);
				break;
			case STREAMREQ:
				//Pedido de Stream
				processRequest((StreamReq *) gmsg);
				break;
			case PEEREXIT:
				//Desconexão de um peer
				disconnectPeer((PeerExit *) gmsg);
				break;
			default:
				break;
		}
		
	}
	delete msg;
}
